package cn.iocoder.yudao.module.system.service.auth;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginReqVO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.portal.PortalUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
import cn.iocoder.yudao.module.system.service.auth.PortalAuthService;
import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
import cn.iocoder.yudao.module.system.service.portal.PortalUserService;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Validator;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;

@Service
@Slf4j
@RequiredArgsConstructor
public class PortalAuthServiceImpl implements PortalAuthService {

    // 复用后台Bean（与接口定义一致）
    private final Validator validator;
    private final CaptchaService captchaService;
    private final LoginLogService loginLogService;
    private final OAuth2TokenService oauth2TokenService;
    private final PortalUserService portalUserService;
    @Resource
    private final cn.iocoder.yudao.framework.common.util.rsa.RsaUtils rsaUtils;

    // 验证码开关（读取配置文件）
    @Value("${yudao.captcha.enable:true}")
    private Boolean captchaEnable;

    // ========== 门户用户存储常量（独立于后台，避免冲突） ==========
    /** 门户用户对象在Request中的存储Key */
    private static final String PORTAL_LOGIN_USER_ATTR = "PORTAL_LOGIN_USER";
    /** 门户用户ID在Request中的存储Key */
    private static final String PORTAL_LOGIN_USER_ID_ATTR = "PORTAL_LOGIN_USER_ID";

    private static final String KEY = "hnsjtyst@2020com";

    // ==================== 登录核心逻辑 ====================
    @Override
    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
        // 1. 校验图形验证码（与后台逻辑一致）
        validateCaptcha(reqVO);
        // 2. 账号密码认证（适配门户用户表）
        PortalUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());
        // 3. 生成令牌+记录日志+构建门户用户信息（不影响后台）
        return createTokenAfterLoginSuccess(user, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
    }

    // ==================== 刷新令牌逻辑 ====================
    @Override
    public AuthLoginRespVO refreshToken(String refreshToken) {
        // 1. 调用后台刷新令牌接口（使用门户专属客户端ID）
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(
                refreshToken, OAuth2ClientConstants.CLIENT_ID_PORTAL);

        // 2. 校验令牌所属用户类型（防止后台/会员用户越权）
        if (accessTokenDO == null || !UserTypeEnum.PORTAL_USER.getValue().equals(accessTokenDO.getUserType())) {
            throw exception(ErrorCodeConstants.AUTH_REFRESH_TOKEN_INVALID);
        }

        // 3. 刷新令牌时，重新构建门户用户信息并存入Request
        PortalUserDO portalUser = portalUserService.getUserById(accessTokenDO.getUserId());
        if (portalUser != null) {
            buildAndSetPortalLoginUser(portalUser, accessTokenDO.getExpiresTime());
        }

        // 4. 转换为统一返回VO
        return cn.iocoder.yudao.module.system.convert.auth.AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    // ==================== 登出逻辑 ====================
    @Override
    public void logout(String token, Integer logType) {
        // 1. 删除访问令牌（复用后台令牌服务）
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
        if (accessTokenDO == null) {
            log.warn("门户用户登出：令牌不存在，token={}", token);
            return;
        }

        // 2. 仅处理门户用户的登出日志
        if (UserTypeEnum.PORTAL_USER.getValue().equals(accessTokenDO.getUserType())) {
            createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
            // 3. 登出时清除Request中的门户用户信息（关键：避免残留）
            clearPortalLoginUserFromRequest();
        }
    }

    // ==================== 私有工具方法 ====================
    /**
     * 门户用户账号密码认证
     */
    @Override
    public PortalUserDO authenticate(String username, String password) {
        final LoginLogTypeEnum logType = LoginLogTypeEnum.LOGIN_USERNAME;

        // 1. 查询未删除的门户用户
        PortalUserDO user = portalUserService.getUserByUsername(username);
        if (user == null) {
            createLoginLog(null, username, logType, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(ErrorCodeConstants.AUTH_PORTAL_USER_NOT_EXISTS);
        }

        // 2. RSA解密前端密码
        String rawPassword = rsaUtils.decryptByPrivateKey(password);
        // 3. 校验密码
        if (!portalUserService.validatePassword(rawPassword, user.getPassword())) {
            createLoginLog(user.getId(), username, logType, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS);
        }

        // 4. 校验用户删除状态
        if ("1".equals(user.getDelFlag())) {
            createLoginLog(user.getId(), username, logType, LoginResultEnum.USER_DISABLED);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED);
        }

        return user;
    }

    /**
     * 登录成功后：生成令牌+记录日志+构建门户用户信息（核心修改：不影响后台）
     */
    @Override
    public AuthLoginRespVO createTokenAfterLoginSuccess(PortalUserDO portalUser, String username, LoginLogTypeEnum logType) {
        // 1. 构建门户用户信息并存入Request（不覆盖后台的SecurityContext）
        buildAndSetPortalLoginUser(portalUser, null);

        // 2. 生成OAuth2令牌（修复scopes参数：传空集合）
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(
                portalUser.getId(),
                UserTypeEnum.PORTAL_USER.getValue(),
                OAuth2ClientConstants.CLIENT_ID_PORTAL,
               null
        );

        // 3. 更新门户用户的过期时间（与令牌一致）
        if (accessTokenDO != null && accessTokenDO.getExpiresTime() != null) {
            LoginUser portalLoginUser = getPortalLoginUserFromRequest();
            if (portalLoginUser != null) {
                portalLoginUser.setExpiresTime(accessTokenDO.getExpiresTime());
                // 重新存入Request
                HttpServletRequest request = ServletUtils.getRequest();
                if (request != null) {
                    request.setAttribute(PORTAL_LOGIN_USER_ATTR, portalLoginUser);
                }
            }
        }

        // 4. 记录登录成功日志+更新登录信息
        createLoginLog(portalUser.getId(), username, logType, LoginResultEnum.SUCCESS);
        portalUserService.updateUserLoginInfo(portalUser.getId(), getClientIP());

        // 5. 转换返回VO
        return cn.iocoder.yudao.module.system.convert.auth.AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    @Override
    public Boolean identityAuthenticate(String username, String password) {
        log.info("===============[identityAuthenticate][username: {}, password: {}]", username, password);

        String pwd = this.decryptAES(password);
        PortalUserDO user = portalUserService.getUserByUsername(username);
        if (user == null) {
            return false;
        }
        // 校验是否禁用
        return portalUserService.validatePassword(pwd, user.getPassword());
    }

    public String decryptAES(String data) {

        try {
            byte[] encrypted = new Base64().decode(data);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            SecretKeySpec keyspec = new SecretKeySpec(KEY.getBytes("UTF-8"),
                    "AES");
            cipher.init(Cipher.DECRYPT_MODE, keyspec);
            byte[] original = cipher.doFinal(encrypted);
            String originalString = new String(original,
                    "UTF-8");
            return originalString.trim();
        }
        catch (Exception e) {
            return null;
        }
    }

    /**
     * 构建门户用户对象并存入Request（不依赖WebFrameworkUtils）
     */
    private void buildAndSetPortalLoginUser(PortalUserDO portalUser, LocalDateTime expiresTime) {
        HttpServletRequest request = ServletUtils.getRequest();
        if (request == null) {
            log.warn("构建门户用户信息失败：Request为空");
            return;
        }

        // 1. 构建门户用户的LoginUser对象
        LoginUser portalLoginUser = new LoginUser();
        portalLoginUser.setId(portalUser.getId()); // 门户用户ID
        portalLoginUser.setUserType(UserTypeEnum.PORTAL_USER.getValue()); // 标记为门户用户
        portalLoginUser.setTenantId(null); // 无多租户设为null
        portalLoginUser.setVisitTenantId(null);
        portalLoginUser.setExpiresTime(expiresTime != null ? expiresTime : LocalDateTime.now().plusHours(2)); // 默认2小时过期
        portalLoginUser.setScopes(Collections.emptyList()); // 授权范围设为空列表

        // 2. 构建扩展信息（存入info，包含用户名、昵称等）
        Map<String, String> userInfo = new HashMap<>();
        userInfo.put(LoginUser.INFO_KEY_NICKNAME, portalUser.getNickName()); // 昵称
        userInfo.put("username", portalUser.getName()); // 用户名（因LoginUser无userName字段）
        userInfo.put("telephone", portalUser.getTelePhone()); // 手机号
        userInfo.put("avatar", null); // 头像（无则设为null）
        userInfo.put("delFlag", portalUser.getDelFlag()); // 删除状态
        portalLoginUser.setInfo(userInfo);

        // 3. 存入Request的attribute（独立存储，不影响后台）
        request.setAttribute(PORTAL_LOGIN_USER_ATTR, portalLoginUser);
        request.setAttribute(PORTAL_LOGIN_USER_ID_ATTR, portalUser.getId());
    }

    /**
     * 从Request中获取门户用户对象
     */
    private LoginUser getPortalLoginUserFromRequest() {
        HttpServletRequest request = ServletUtils.getRequest();
        if (request == null) {
            return null;
        }
        return (LoginUser) request.getAttribute(PORTAL_LOGIN_USER_ATTR);
    }

    /**
     * 清除Request中的门户用户信息（登出时调用）
     */
    private void clearPortalLoginUserFromRequest() {
        HttpServletRequest request = ServletUtils.getRequest();
        if (request != null) {
            request.removeAttribute(PORTAL_LOGIN_USER_ATTR);
            request.removeAttribute(PORTAL_LOGIN_USER_ID_ATTR);
        }
    }

    /**
     * 记录登录日志
     */
    private void createLoginLog(Long userId, String username, LoginLogTypeEnum logType, LoginResultEnum result) {
        String userIp = getClientIP();
        if (ObjectUtil.isEmpty(userIp)) {
            userIp = "未知IP";
        }

        LoginLogCreateReqDTO logReq = new LoginLogCreateReqDTO();
        logReq.setLogType(logType.getType());
        logReq.setTraceId(TracerUtils.getTraceId());
        logReq.setUserId(userId);
        logReq.setUserType(UserTypeEnum.PORTAL_USER.getValue());
        logReq.setUsername(username);
        logReq.setResult(result.getResult());
        logReq.setUserIp(userIp);
        logReq.setUserAgent(ServletUtils.getUserAgent());

        loginLogService.createLoginLog(logReq);
    }

    /**
     * 记录登出日志
     */
    private void createLogoutLog(Long userId, Integer userType, Integer logType) {
        PortalUserDO user = portalUserService.getUserById(userId);
        String username = (user != null) ? user.getName() : "未知门户用户";
        String userIp = getClientIP();
        if (ObjectUtil.isEmpty(userIp)) {
            userIp = "未知IP";
        }

        LoginLogCreateReqDTO logReq = new LoginLogCreateReqDTO();
        logReq.setLogType(logType);
        logReq.setTraceId(TracerUtils.getTraceId());
        logReq.setUserId(userId);
        logReq.setUserType(userType);
        logReq.setUsername(username);
        logReq.setResult(LoginResultEnum.SUCCESS.getResult());
        logReq.setUserIp(userIp);
        logReq.setUserAgent(ServletUtils.getUserAgent());

        loginLogService.createLoginLog(logReq);
    }

    /**
     * 校验图形验证码
     */
    private void validateCaptcha(AuthLoginReqVO reqVO) {
        if (!captchaEnable) {
            return;
        }

        ResponseModel response = doValidateCaptcha(reqVO);
        if (!response.isSuccess()) {
            createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);
            throw exception(ErrorCodeConstants.AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());
        }
    }

    /**
     * 验证码校验实现
     */
    private ResponseModel doValidateCaptcha(AuthLoginReqVO reqVO) {
        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
        return captchaService.verification(captchaVO);
    }
}